/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sg_main.cpp - main server-game file
//


#include "sg_local.h"


levelLocals_t			level;

glqServer_t *			glqServer;

cvar_t *				sg_showEntities;
cvar_t *				sg_maxShowDistance;
cvar_t *				sg_debugMove;
cvar_t *				sg_gravity;
cvar_t *				sg_filterBan;
cvar_t *				sg_gameMode;


/*
 ==================
 
 ==================
*/
static void SG_LoadLevel (bool restarting){

	etEntity	*entity;
	char		name[MAX_PATH_LENGTH];
	int			time;
	int			i;

	time = glqSystem->Milliseconds();

	level.state = GS_LOADING;
	level.restarting = restarting;

	level.allowFrees = true;

	G_Printf("Loading %s\n", level.name);

	// Load the clip map
	Str_SPrintf(name, sizeof(name), "maps/%s.bsp", level.name);

	glqCollision->LoadMap(name);

	// Create clip sectors
	SG_CreateClipSectors();

	// Spawn all the entities
	SG_SpawnEntities(glqCollision->EntityString());
	SG_SpawnLightAndSpeakerEntities();

	// Now that all the entities are spawned, it is ok to perform some
	// additional setup
	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse)
			continue;

		// Bind to another entity if desired
		entity->Bind();
	}

	// Defer freeing any entities that are removed during their final setup
	level.allowFrees = false;

	// Some entities might have been removed during their final setup, so free
	// them for real
	SG_ActuallyFreeEntities();

	// We're done loading the level
	level.state = GS_ACTIVE;
	level.mode = GM_SINGLEPLAYER;		// FIXME: not sure where this should properly be set, use sg_gameMode if the user wants to change
	level.restarting = false;

	G_Printf("%.2f seconds to load %s\n", MS2SEC(glqSystem->Milliseconds() - time), level.name);
}

/*
 ==================
 SG_SortActiveEntityList
 ==================
*/
static int SG_SortActiveEntityList (const void *elem1, const void *elem2){

	const etEntity	*entity1 = *(const etEntity **)elem1;
	const etEntity	*entity2 = *(const etEntity **)elem2;

	return entity1->sort - entity2->sort;
}

/*
 ==================
 SG_BuildActiveEntityList
 ==================
*/
static void SG_BuildActiveEntityList (){

	etEntity	*entity;
	int			i;

	// Build the active entity list
	level.numActiveEntities = 0;

	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse)
			continue;

		// Set the sort value
		entity->SetSort();

		// Add it to the active entity list
		level.activeEntities[level.numActiveEntities++] = entity;
	}

	// Sort the active entity list so that masters come before slaves and
	// players always come first
	qsort(level.activeEntities, level.numActiveEntities, sizeof(etEntity *), SG_SortActiveEntityList);
}


/*
 ==============================================================================

 PUBLIC INTERFACE

 ==============================================================================
*/


/*
 ==================
 SG_ClientCommand
 ==================
*/
static void SG_ClientCommand (int clientNum){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientCommand: clientNum out of range");

	level.clients[clientNum].Command();
}

/*
 ==================
 SG_ClientConnect
 ==================
*/
static const char *SG_ClientConnect (int clientNum, bool localClient, const char *ip, char *userInfo, bool firstTime){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientConnect: clientNum out of range");

	return level.clients[clientNum].Connect(clientNum, localClient, ip, userInfo, firstTime);
}

/*
 ==================
 SG_ClientDisconnect
 ==================
*/
static void SG_ClientDisconnect (int clientNum){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientDisconnect: clientNum out of range");

	level.clients[clientNum].Disconnect();
}

/*
 ==================
 SG_ClientBegin
 ==================
*/
static void SG_ClientBegin (int clientNum){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientBegin: clientNum out of range");

	level.clients[clientNum].Begin();
}

/*
 ==================
 SG_ClientThink
 ==================
*/
static void SG_ClientThink (int clientNum, const userCmd_t *userCmd){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientThink: clientNum out of range");

	level.clients[clientNum].Think(userCmd);
}

/*
 ==================
 SG_ClientWriteGameState
 ==================
*/
static void SG_ClientWriteGameState (int clientNum, msg_t *msg, int offset){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientWriteGameState: clientNum out of range");

	level.clients[clientNum].WriteGameState(msg, offset);
}

/*
 ==================
 SG_ClientWriteSnapshot
 ==================
*/
static void SG_ClientWriteSnapshot (int clientNum, msg_t *msg, int sequence, int deltaMessage, bool rateDelayed){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientWriteSnapshot: clientNum out of range");

	level.clients[clientNum].WriteSnapshot(msg, sequence, deltaMessage, rateDelayed);
}

/*
 ==================
 SG_ClientSetPing
 ==================
*/
static void SG_ClientSetPing (int clientNum, int ping){

	if (clientNum < 0 || clientNum >= MAX_CLIENTS)
		G_Error("SG_ClientSetPing: clientNum out of range");

	level.clients[clientNum].SetPing(ping);
}

/*
 ==================
 SG_CreateBaselines

 Entity baselines are used to compress non-delta messages to the clients. Only
 the fields that differ from the baseline will be transmitted.
 ==================
*/
static void SG_CreateBaselines (){

	etEntity	*entity;
	int			i;

	for (i = 0, entity = level.entities; i < level.numEntities; i++, entity++){
		if (!entity->inUse)
			continue;

		// Take current state as baseline
		glqMemory->Copy(&level.baselines[i], &entity->state, sizeof(entityState_t));
	}
}


/*
 ==============================================================================

 FRAMEWORK

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
static void SG_RunFrame (){

	etEntity	*entity;
	int			i;

	level.time += GAME_FRAMEMSEC;

	// Build the active entity list
	SG_BuildActiveEntityList();

	// Go through all the active entities
	for (i = 0; i < level.numActiveEntities; i++){
		entity = level.activeEntities[i];

		if (entity->client)
			continue;

		// Run physics
		entity->RunPhysics();

		// Run thinking code
		entity->RunThink();
	}
}

/*
 ==================
 SG_Redraw
 ==================
*/
static void SG_Redraw (){

	// Draw debug tools
	SG_DrawDebugTools();
	AI_DrawDebugTools();

	// Refresh or close the integrated editors
	if (level.localClient){
		SG_RefreshLightEditor();
		SG_RefreshSpeakerEditor();
	}
	else {
		SG_CloseLightEditor();
		SG_CloseSpeakerEditor();
	}
}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
void SG_Init (const char *levelName, int levelTime){

	G_Printf("--------- Game Initialization ---------\n");
	G_Printf("Game name: %s\n", GAME_NAME);
	G_Printf("Game date: %s\n", __DATE__);

	// Initialize game
	G_Init(false);

	// Register variables
	sg_showEntities = glqCVarSystem->Register("sg_showEntities", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Draw the entities", 0, 0);
	sg_maxShowDistance = glqCVarSystem->Register("sg_maxShowDistance", "500.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Draw this many units from the view", 0.0f, 0.0f);	
	sg_debugMove = glqCVarSystem->Register("sg_debugMove", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Debug player movement", 0, 0);
	sg_gravity = glqCVarSystem->Register("sg_gravity", Str_FromFloat(DEFAULT_GRAVITY, -1), CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "World gravity", 1.0f, 5000.0f);
	sg_filterBan = glqCVarSystem->Register("sg_filterBan", "1", CVAR_BOOL, CVAR_ARCHIVE | CVAR_GAME, "Filter banned IPs", 0, 0);
	sg_gameMode = glqCVarSystem->Register("sg_gameMode", "0", CVAR_INTEGER, CVAR_ARCHIVE | CVAR_GAME, "Sets the game mode (0 = singleplayer, 1 = deathmatch, 2 = coop)", 0, 2);

	// Add commands
	SG_AddCommands();

	// Set the level name
	Str_Copy(level.name, levelName, sizeof(level.name));

	// Set the level time
	level.time = levelTime;

	// Always leave room for the max number of clients, even if they aren't all
	// used, so numbers inside that range are never anything but clients
	level.numEntities = MAX_CLIENTS;

	// Allocate the clients
	level.clients = (etClient *)glqMemory->ClearedAlloc(MAX_CLIENTS * sizeof(etClient), TAG_SERVER);

	// Allocate the entities
	level.entities = (etEntity *)glqMemory->ClearedAlloc(MAX_ENTITIES * sizeof(etEntity), TAG_SERVER);

	// Initialize the integrated editors
	SG_InitLightEditor();
	SG_InitSpeakerEditor();

	// Initialize clip sectors
	SG_InitClipSectors();

	// Parse banned IPs
	SG_ParseBanIPs();

	// Load the level
	SG_LoadLevel(false);
}

/*
 ==================
 SG_Shutdown
 ==================
*/
void SG_Shutdown (){

	// Shutdown clip sectors
	SG_ShutdownClipSectors();

	// Shutdown the integrated editors
	SG_ShutdownSpeakerEditor();
	SG_ShutdownLightEditor();

	// Remove commands
	SG_RemoveCommands();

	// Shutdown game
	G_Shutdown(false);
}


// ============================================================================


/*
 ==================
 
 ==================
*/
glqSGame_t *GetSGameAPI (glqServer_t *import){

	static glqSGame_t	glqSGame;

	glqServer						= import;

	glqCommon						= &import->glqCommon;
	glqMemory						= &import->glqMemory;
	glqFileSystem					= &import->glqFileSystem;
	glqCmdSystem					= &import->glqCmdSystem;
	glqCVarSystem					= &import->glqCVarSystem;
	glqMsgSystem					= &import->glqMsgSystem;
	glqDefManager					= &import->glqDefManager;
	glqParser						= &import->glqParser;
	glqSystem						= &import->glqSystem;
	glqTable						= &import->glqTable;
	glqCollision					= &import->glqCollision;
	glqRenderer						= &import->glqRenderer;
	glqSound						= &import->glqSound;
	glqGUI							= &import->glqGUI;

	// Set up the exported functions
	glqSGame.apiVersion				= SGAME_API_VERSION;

	glqSGame.ClientCommand			= SG_ClientCommand;
	glqSGame.ClientConnect			= SG_ClientConnect;
	glqSGame.ClientDisconnect		= SG_ClientDisconnect;
	glqSGame.ClientBegin			= SG_ClientBegin;
	glqSGame.ClientThink			= SG_ClientThink;
	glqSGame.ClientWriteGameState	= SG_ClientWriteGameState;
	glqSGame.ClientWriteSnapshot	= SG_ClientWriteSnapshot;
	glqSGame.ClientSetPing			= SG_ClientSetPing;
	glqSGame.CreateBaselines		= SG_CreateBaselines;
	glqSGame.RunFrame				= SG_RunFrame;
	glqSGame.Redraw					= SG_Redraw;
	glqSGame.Init					= SG_Init;
	glqSGame.Shutdown				= SG_Shutdown;

	return &glqSGame;
}